
// This file is part of Module Proxy.

// Module Proxy is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Module Proxy is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Module Proxy.  If not, see <https://www.gnu.org/licenses/>.


//         Copyright (C) 2021 - 2030  关中麦客  
//         All rights reserved
//
//         main.rs
//         程序入口，启动HTTP服务，并进行初步的模块匹配
//
//         created by 关中麦客 1036038462@qq.com


use log;
use log4rs;
use log::LevelFilter;
use log4rs::encode::pattern::PatternEncoder;
use log4rs::config::{Appender, Config, Root};
use log4rs::append::rolling_file::policy::compound::roll::fixed_window::FixedWindowRoller;
use log4rs::append::rolling_file::policy::compound::trigger::size::SizeTrigger;
use log4rs::append::rolling_file::policy::compound::CompoundPolicy;
use log4rs::filter::threshold::ThresholdFilter;
use log4rs::append::rolling_file::RollingFileAppender;

use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response, Server, Error}; 
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::path::Path;
use once_cell::sync::OnceCell;

mod response;
mod conf;
mod mod_socket;
mod mod_http; 
mod mod_html;
mod mod_url;
mod mod_urlmap;
mod mod_uuid;
mod mod_uuidmap;
mod mod_uuidforward;
mod util;

static _HOME_PATH: OnceCell<String> = OnceCell::new();   //全局变量HOME_PATH（安装路径）
static _ROOT_PATH: OnceCell<String> = OnceCell::new();   //全局变量ROOT_PATH（html根路径）

pub const INNER_MODULE: &str = "module_proxy_inner_mod"; //内部代理模块
pub const VERSION: &str = "Module Proxy v0.5";    //版本

#[tokio::main]
async fn main() 
{   
    //缓存HOME_PATH------------
    init_home_path();
    println!("\nHOME_PATH: {}", home_path());  

    //加载配置文件------------
    if let Err(err_string) = conf::load()
    {
        println!("Error: {}", err_string);
        std::process::exit(1);   //exit
    }

    //缓存ROOT_PATH------------
    init_root_path();
    println!("ROOT_PATH: {}", root_path());

    //初始化log4rs------------
    let log_level = conf::log_level();  //日志级别
    let lvl = match log_level{
        "info" => LevelFilter::Info,
        "warn"  => LevelFilter::Warn,
        "error" => LevelFilter::Error,
        _       => LevelFilter::Debug,
    };
    log_init(lvl);
    log::info!("Log Level: {}", lvl);

    //初始化唯一URL模块------------
    mod_urlmap::init();
    mod_url::init().await;

    //初始化uuid auth模块------------
    mod_uuidmap::init();
    mod_uuid::init().await;
    
    //初始化socket转发集群---------
    mod_socket::init_cluster();
    mod_socket::print_cache();
    //初始化http反向代理转发集群---------
    mod_http::init_cluster();
    mod_http::print_cache();

    //启动http监听------------
    let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), conf::server_port());
    // hyper服务
    let make_svc = make_service_fn(|_| async { Ok::<_, hyper::Error>(service_fn(do_req)) });
    let server = Server::bind(&addr).serve(make_svc);

    println!("");
    println!("===============================================================");
    println!("  ___  ___          _       _       ______                     ");
    println!("  |  \\/  |         | |     | |      | ___ \\                    ");
    println!("  | .  . | ___   __| |_   _| | ___  | |_/ / __ _____  ___   _  ");
    println!("  | |\\/| |/ _ \\ / _` | | | | |/ _ \\ |  __/ '__/ _ \\ \\/ / | | | ");
    println!("  | |  | | (_) | (_| | |_| | |  __/ | |  | | | (_) >  <| |_| | ");
    println!("  \\_|  |_/\\___/ \\__,_|\\__,_|_|\\___| \\_|  |_|  \\___/_/\\_\\__,  | ");
    println!("                                                         __/ | ");
    println!("                                                        |___/  ");
    println!("===============================================================");
    println!("{} start...", VERSION);
    println!("Listening on http://{}", addr);
    println!("");

    log::info!("");
    log::info!("===============================================================");
    log::info!("{} start...", VERSION);
    log::info!("Listening on http://{}", addr);
    log::info!("home path: {}", home_path());
    log::info!("===============================================================");
    log::info!("");

    if let Err(e) = server.await 
    {
        log::error!("[main] hyper server error: {}", e);
    }
}

async fn do_req(req: Request<Body>) -> Result<Response<Body>, Error> 
{
    log::debug!("[main] url path: {}", req.uri().path());
    let rcv_url = err_recovery(req.uri().path());   //容错： URL起始'//'的URL，修改为'/'

    if let Some(mod_name) = module(rcv_url)
    {
        if mod_name == INNER_MODULE  //内置模块
        {
            log::debug!("[main] default proxy");
            let remove_str = format!("/{}/", INNER_MODULE);
            let filename = rcv_url.replace(&remove_str, "");
            return mod_html::default_html_file(&filename).await;
        }
        else if mod_uuid::exist(&mod_name)  //auth uuid
        {
            log::debug!("[main] uuid auth proxy"); 
            return mod_uuid::forward(req, &mod_name).await;
        }
        else if mod_socket::exist(&mod_name)  //socket代理
        {
            log::debug!("[main] socket proxy");       
            return mod_socket::socket(req, &mod_name).await;
        }
        else if mod_http::exist(&mod_name)   //http反向代理
        {
            log::debug!("[main] http proxy");            
            return mod_http::forward(req, &mod_name).await;
        } 
        else if mod_url::exist(&mod_name)   //唯一URL
        {
            log::debug!("[main] mod url proxy");
            return mod_url::forward(req, &mod_name).await;
        }
        else if let Some(module_html) = conf::module_html(&mod_name)  //虚拟网站代理
        {
            log::debug!("[main] html proxy");           
            return mod_html::module_file(&module_html.path, rcv_url, &mod_name).await;
        }
    }

    //未匹配到模块，到网站根目录
    log::debug!("[main] none proxy");
    if root_path() != ""
    {
        return mod_html::file(root_path(), rcv_url).await;
    }
    
    Ok(response::rsp_500().await)
}

/// 容错：起始'//'的URL，修改为'/'
pub fn err_recovery(url: &str) -> &str
{
    //容错：URL起始的'//'当作'/'
    let arr = url.as_bytes();
    if arr.len() >= 2
    {
        if arr[0] == b'/' && arr[1] == b'/'
        {
            let rcv_url = &url[1..];
            return rcv_url;
        }
    }

    url
}

/// 从URL中获得mod_name
pub fn module(url: &str) -> Option<String>
{
    //查询出module名称
    for (i, c) in url.char_indices() 
    {
        if c == '/' && i > 0  //找到第二个'/'
        {
            let module = &url[1..i];            
              
            return Some(module.to_string());
        }
    }
    
    None
}

//init HOME_PATH
fn init_home_path()
{
    if let Ok(exe_path) = std::env::current_exe()               //exe路径
    {
        if let Some(parent_path) = exe_path.parent()            //exe所在目录
        {
            if let Some(parent_path) = parent_path.parent()     //上一级目录
            {
                if let Some(home_path) = parent_path.to_str()
                {
                    _HOME_PATH.get_or_init(||{
                        home_path.to_string()
                    });    
                    return;                
                }
            }                        
        }
    }

    println!("Error: Can't get Module Proxy home path.");
    std::process::exit(1);
}

//init html'根'路径
fn init_root_path()
{
    //'html'配置
    if conf::root_path() == "html"
    {
        let home = home_path();
        let home_path = Path::new(home);
        let html_path = home_path.join("html");
        if html_path.is_dir()
        {
            let html_path = html_path.to_str().unwrap();
            let root_path = html_path.replace("\\", "/");
            _ROOT_PATH.get_or_init(||{
                root_path
            });
            return;
        }

        log::warn!("Can't init root_path, The installation path must contain an 'html' directory");
        _ROOT_PATH.get_or_init(||{
            "".to_string()
        });
        return;
    }

    //指定目录配置
    let root_path = Path::new(conf::root_path());
    if root_path.is_dir() 
    {
        let path = root_path.to_str().unwrap();
        let root_path = path.replace("\\", "/");
        _ROOT_PATH.get_or_init(||{
            root_path
        });
        return;
    }

    log::warn!("The root_path configured in the default.toml does not exist");
    _ROOT_PATH.get_or_init(||{
        "".to_string()
    });    
}

/// 获得HOME_PATH
pub fn home_path() -> &'static String
{
    _HOME_PATH.get().unwrap()
}

/// 获得html'根'路径
fn root_path() -> &'static String
{
    _ROOT_PATH.get().unwrap()
}

/// 日志初始化
fn log_init(lvl: LevelFilter)
{
    //日志目录
    let home = home_path();
    let path = Path::new(home);
    let log_path = path.join("logs");
    //日志文件
    let mut file_path = log_path.clone();
    file_path.push("mod_proxy.log");
    let filename = file_path.to_str().unwrap();
    let filename = filename.replace("\\", "/");
    //备份文件
    let mut bak_path = log_path.clone();
    bak_path.push("mod_proxy{}.log");
    let bakfilename = bak_path.to_str().unwrap();
    let bakfilename = bakfilename.replace("\\", "/");

    //log文件备份数量
    let window_size = 5; 
    let fixed_window_roller = 
        FixedWindowRoller::builder().build(&bakfilename, window_size).unwrap();

    //log文件大小
    let size_limit = 10 * 1024 * 1024; // 循环日志文件最大10M
    let size_trigger = SizeTrigger::new(size_limit);

    let compound_policy = CompoundPolicy::new(Box::new(size_trigger),Box::new(fixed_window_roller));

    let file = RollingFileAppender::builder()
        .encoder(Box::new(PatternEncoder::new("{d(%Y-%m-%d %H:%M:%S)} {l} - {m}{n}")))
        .build(filename, Box::new(compound_policy))
        .unwrap();

    let config = Config::builder()
        .appender(
            Appender::builder()
                .filter(Box::new(ThresholdFilter::new(LevelFilter::Debug)))
                .build("file", Box::new(file))
        )
        .build(
            Root::builder().appender("file").build(lvl)
        )
        .unwrap();

    log4rs::init_config(config).unwrap();
}